/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved	*/


/*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
/*	  All Rights Reserved	*/

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2018, Joyent, Inc.
 */

#define	_LARGEFILE64_SOURCE

/* Get definitions for the relocation types supported. */
#define	ELF_TARGET_ALL

#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <libelf.h>
#include <stdlib.h>
#include <limits.h>
#include <locale.h>
#include <wctype.h>
#include <string.h>
#include <errno.h>
#include <door.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mkdev.h>
#include <sys/stat.h>
#include <sys/elf.h>
#include <procfs.h>
#include <sys/core.h>
#include <sys/dumphdr.h>
#include <netinet/in.h>
#include <gelf.h>
#include <elfcap.h>
#include <sgsrtcid.h>
#include "file.h"
#include "elf_read.h"

/*
 *	Misc
 */

#define	FBSZ		512
#define	MLIST_SZ	12

/*
 * The 0x8FCA0102 magic string was used in crash dumps generated by releases
 * prior to Solaris 7.
 */
#define	OLD_DUMP_MAGIC	0x8FCA0102

#if defined(__sparc)
#define	NATIVE_ISA	"SPARC"
#define	OTHER_ISA	"Intel"
#else
#define	NATIVE_ISA	"Intel"
#define	OTHER_ISA	"SPARC"
#endif

/* Assembly language comment char */
#ifdef pdp11
#define	ASCOMCHAR '/'
#else
#define	ASCOMCHAR '!'
#endif

#pragma	align	16(fbuf)
static char	fbuf[FBSZ];

/*
 * Magic file variables
 */
static intmax_t maxmagicoffset;
static intmax_t tmpmax;
static char	*magicbuf;

static char	*dfile;
static char	*troff[] = {	/* new troff intermediate lang */
		"x", "T", "res", "init", "font", "202", "V0", "p1", 0};

static char	*fort[] = {			/* FORTRAN */
		"function", "subroutine", "common", "dimension", "block",
		"integer", "real", "data", "double",
		"FUNCTION", "SUBROUTINE", "COMMON", "DIMENSION", "BLOCK",
		"INTEGER", "REAL", "DATA", "DOUBLE", 0};

static char	*asc[] = {		/* Assembler Commands */
		"sys", "mov", "tst", "clr", "jmp", "cmp", "set", "inc",
		"dec", 0};

static char	*c[] = {			/* C Language */
		"int", "char", "float", "double", "short", "long", "unsigned",
		"register", "static", "struct", "extern", 0};

static char	*as[] = {	/* Assembler Pseudo Ops, prepended with '.' */
		"globl", "global", "ident", "file", "byte", "even",
		"text", "data", "bss", "comm", 0};

/*
 * The line and debug section names are used by the strip command.
 * Any changes in the strip implementation need to be reflected here.
 */
static char	*debug_sections[] = { /* Debug sections in a ELF file */
		".debug", ".stab", ".dwarf", ".line", NULL};

/* start for MB env */
static wchar_t	wchar;
static int	length;
static int	IS_ascii;
static int	Max;
/* end for MB env */
static int	i;	/* global index into first 'fbsz' bytes of file */
static int	fbsz;
static int	ifd = -1;
static int	elffd = -1;
static int	tret;
static int	hflg;
static int	dflg;
static int	mflg;
static int	M_flg;
static int	iflg;
static struct stat64	mbuf;

static char	**mlist1;	/* 1st ordered list of magic files */
static char	**mlist2;	/* 2nd ordered list of magic files */
static size_t	mlist1_sz;	/* number of ptrs allocated for mlist1 */
static size_t	mlist2_sz;	/* number of ptrs allocated for mlist2 */
static char	**mlist1p;	/* next entry in mlist1 */
static char	**mlist2p;	/* next entry in mlist2 */

static ssize_t	mread;

static void ar_coff_or_aout(int ifd);
static int type(char *file);
static int def_position_tests(char *file);
static void def_context_tests(void);
static int troffint(char *bp, int n);
static int lookup(char **tab);
static int ccom(void);
static int ascom(void);
static int sccs(void);
static int english(char *bp, int n);
static int shellscript(char buf[], struct stat64 *sb);
static int elf_check(char *file);
static int get_door_target(char *, char *, size_t);
static int zipfile(char *, int);
static int is_crash_dump(const char *, int);
static void print_dumphdr(const int, const dumphdr_t *, uint32_t (*)(uint32_t),
    const char *);
static uint32_t swap_uint32(uint32_t);
static uint32_t return_uint32(uint32_t);
static void usage(void);
static void default_magic(void);
static void add_to_mlist(char *, int);
static void fd_cleanup(void);
static int is_rtld_config(void);

/* from elf_read.c */
int elf_read32(int elffd, Elf_Info *EInfo);
int elf_read64(int elffd, Elf_Info *EInfo);

#ifdef XPG4
	/* SUSv3 requires a single <space> after the colon */
#define	prf(x)	(void) printf("%s: ", x);
#else	/* !XPG4 */
#define	prf(x)	(void) printf("%s:%s", x, (int)strlen(x) > 6 ? "\t" : "\t\t");
#endif	/* XPG4 */

/*
 * Static program identifier - used to prevent localization of the name "file"
 * within individual error messages.
 */
const char *File = "file";

int
main(int argc, char **argv)
{
	char	*p;
	int	ch;
	FILE	*fl;
	int	bflg = 0;
	int	cflg = 0;
	int	eflg = 0;
	int	fflg = 0;
	char	*ap = NULL;
	int	pathlen;
	char	**filep;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	while ((ch = getopt(argc, argv, "M:bcdf:him:")) != EOF) {
		switch (ch) {

		case 'M':
			add_to_mlist(optarg, !dflg);
			M_flg++;
			break;

		case 'b':
			bflg++;
			break;

		case 'c':
			cflg++;
			break;

		case 'd':
			if (!dflg) {
				default_magic();
				add_to_mlist(dfile, 0);
				dflg++;
			}
			break;

		case 'f':
			fflg++;
			errno = 0;
			if ((fl = fopen(optarg, "r")) == NULL) {
				int err = errno;
				(void) fprintf(stderr, gettext("%s: cannot "
				    "open file %s: %s\n"), File, optarg,
				    err ? strerror(err) : "");
				usage();
			}
			pathlen = pathconf("/", _PC_PATH_MAX);
			if (pathlen == -1) {
				int err = errno;
				(void) fprintf(stderr, gettext("%s: cannot "
				    "determine maximum path length: %s\n"),
				    File, strerror(err));
				exit(1);
			}
			pathlen += 2; /* for null and newline in fgets */
			if ((ap = malloc(pathlen * sizeof (char))) == NULL) {
				int err = errno;
				(void) fprintf(stderr, gettext("%s: malloc "
				    "failed: %s\n"), File, strerror(err));
				exit(2);
			}
			break;

		case 'h':
			hflg++;
			break;

		case 'i':
			iflg++;
			break;

		case 'm':
			add_to_mlist(optarg, !dflg);
			mflg++;
			break;

		case '?':
			eflg++;
			break;
		}
	}
	if (!cflg && !fflg && (eflg || optind == argc))
		usage();
	if (iflg && (dflg || mflg || M_flg)) {
		usage();
	}
	if ((iflg && cflg) || (cflg && bflg)) {
		usage();
	}

	if (!dflg && !mflg && !M_flg && !iflg) {
	/* no -d, -m, nor -M option; also -i option doesn't need magic  */
		default_magic();
		if (f_mkmtab(dfile, cflg, 0) == -1) {
			exit(2);
		}
	}

	else if (mflg && !M_flg && !dflg) {
	/* -m specified without -d nor -M */

#ifdef XPG4	/* For SUSv3 only */

		/*
		 * The default position-dependent magic file tests
		 * in /etc/magic will follow all the -m magic tests.
		 */

		for (filep = mlist1; filep < mlist1p; filep++) {
			if (f_mkmtab(*filep, cflg, 1) == -1) {
				exit(2);
			}
		}
		default_magic();
		if (f_mkmtab(dfile, cflg, 0) == -1) {
			exit(2);
		}
#else	/* !XPG4 */
		/*
		 * Retain Solaris file behavior for -m before SUSv3,
		 * when the new -d and -M options are not specified.
		 * Use the -m file specified in place of the default
		 * /etc/magic file.  Solaris file will
		 * now allow more than one magic file to be specified
		 * with multiple -m options, for consistency with
		 * other behavior.
		 *
		 * Put the magic table(s) specified by -m into
		 * the second magic table instead of the first
		 * (as indicated by the last argument to f_mkmtab()),
		 * since they replace the /etc/magic tests and
		 * must be executed alongside the default
		 * position-sensitive tests.
		 */

		for (filep = mlist1; filep < mlist1p; filep++) {
			if (f_mkmtab(*filep, cflg, 0) == -1) {
				exit(2);
			}
		}
#endif /* XPG4 */
	} else {
		/*
		 * For any other combination of -d, -m, and -M,
		 * use the magic files in command-line order.
		 * Store the entries from the two separate lists of magic
		 * files, if any, into two separate magic file tables.
		 * mlist1: magic tests executed before default magic tests
		 * mlist2: default magic tests and after
		 */
		for (filep = mlist1; filep && (filep < mlist1p); filep++) {
			if (f_mkmtab(*filep, cflg, 1) == -1) {
				exit(2);
			}
		}
		for (filep = mlist2; filep && (filep < mlist2p); filep++) {
			if (f_mkmtab(*filep, cflg, 0) == -1) {
				exit(2);
			}
		}
	}

	/* Initialize the magic file variables; check both magic tables */
	tmpmax = f_getmaxoffset(1);
	maxmagicoffset = f_getmaxoffset(0);
	if (maxmagicoffset < tmpmax) {
		maxmagicoffset = tmpmax;
	}
	if (maxmagicoffset < (intmax_t)FBSZ)
		maxmagicoffset = (intmax_t)FBSZ;
	if ((magicbuf = malloc(maxmagicoffset)) == NULL) {
		int err = errno;
		(void) fprintf(stderr, gettext("%s: malloc failed: %s\n"),
		    File, strerror(err));
		exit(2);
	}

	if (cflg) {
		f_prtmtab();
		if (ferror(stdout) != 0) {
			(void) fprintf(stderr, gettext("%s: error writing to "
			    "stdout\n"), File);
			exit(1);
		}
		if (fclose(stdout) != 0) {
			int err = errno;
			(void) fprintf(stderr, gettext("%s: fclose "
			    "failed: %s\n"), File, strerror(err));
			exit(1);
		}
		exit(0);
	}

	for (; fflg || optind < argc; optind += !fflg) {
		register int	l;

		if (fflg) {
			if ((p = fgets(ap, pathlen, fl)) == NULL) {
				fflg = 0;
				optind--;
				continue;
			}
			l = strlen(p);
			if (l > 0)
				p[l - 1] = '\0';
		} else
			p = argv[optind];

		if (!bflg)
			prf(p);		/* print "file_name:<tab>" */

		if (type(p))
			tret = 1;
	}
	if (ap != NULL)
		free(ap);
	if (tret != 0)
		exit(tret);

	if (ferror(stdout) != 0) {
		(void) fprintf(stderr, gettext("%s: error writing to "
		    "stdout\n"), File);
		exit(1);
	}
	if (fclose(stdout) != 0) {
		int err = errno;
		(void) fprintf(stderr, gettext("%s: fclose failed: %s\n"),
		    File, strerror(err));
		exit(1);
	}
	return (0);
}

static int
type(char *file)
{
	int	cc;
	char	buf[BUFSIZ];
	int	(*statf)() = hflg ? lstat64 : stat64;

	i = 0;		/* reset index to beginning of file */
	ifd = -1;
	if ((*statf)(file, &mbuf) < 0) {
		if (statf == lstat64 || lstat64(file, &mbuf) < 0) {
			int err = errno;
			(void) printf(gettext("cannot open: %s\n"),
			    strerror(err));
			return (0);		/* POSIX.2 */
		}
	}
	switch (mbuf.st_mode & S_IFMT) {
	case S_IFREG:
		if (iflg) {
			(void) printf(gettext("regular file\n"));
			return (0);
		}
		break;
	case S_IFCHR:
		(void) printf(gettext("character"));
		goto spcl;

	case S_IFDIR:
		(void) printf(gettext("directory\n"));
		return (0);

	case S_IFIFO:
		(void) printf(gettext("fifo\n"));
		return (0);

	case S_IFLNK:
		if ((cc = readlink(file, buf, BUFSIZ)) < 0) {
			int err = errno;
			(void) printf(gettext("readlink error: %s\n"),
			    strerror(err));
			return (1);
		}
		buf[cc] = '\0';
		(void) printf(gettext("symbolic link to %s\n"), buf);
		return (0);

	case S_IFBLK:
		(void) printf(gettext("block"));
					/* major and minor, see sys/mkdev.h */
spcl:
		(void) printf(gettext(" special (%d/%d)\n"),
		    major(mbuf.st_rdev), minor(mbuf.st_rdev));
		return (0);

	case S_IFSOCK:
		(void) printf("socket\n");
		/* FIXME, should open and try to getsockname. */
		return (0);

	case S_IFDOOR:
		if (get_door_target(file, buf, sizeof (buf)) == 0)
			(void) printf(gettext("door to %s\n"), buf);
		else
			(void) printf(gettext("door\n"));
		return (0);

	}

	if (elf_version(EV_CURRENT) == EV_NONE) {
		(void) printf(gettext("libelf is out of date\n"));
		return (1);
	}

	ifd = open64(file, O_RDONLY);
	if (ifd < 0) {
		int err = errno;
		(void) printf(gettext("cannot open: %s\n"), strerror(err));
		return (0);			/* POSIX.2 */
	}

	/* need another fd for elf, since we might want to read the file too */
	elffd = open64(file, O_RDONLY);
	if (elffd < 0) {
		int err = errno;
		(void) printf(gettext("cannot open: %s\n"), strerror(err));
		(void) close(ifd);
		ifd = -1;
		return (0);			/* POSIX.2 */
	}
	if ((fbsz = read(ifd, fbuf, FBSZ)) == -1) {
		int err = errno;
		(void) printf(gettext("cannot read: %s\n"), strerror(err));
		(void) close(ifd);
		ifd = -1;
		return (0);			/* POSIX.2 */
	}
	if (fbsz == 0) {
		(void) printf(gettext("empty file\n"));
		fd_cleanup();
		return (0);
	}

	/*
	 * First try user-specified position-dependent magic tests, if any,
	 * which need to execute before the default tests.
	 */
	if ((mread = pread(ifd, (void*)magicbuf, (size_t)maxmagicoffset,
	    (off_t)0)) == -1) {
		int err = errno;
		(void) printf(gettext("cannot read: %s\n"), strerror(err));
		fd_cleanup();
		return (0);
	}

	/*
	 * ChecK against Magic Table entries.
	 * Check first magic table for magic tests to be applied
	 * before default tests.
	 * If no default tests are to be applied, all magic tests
	 * should occur in this magic table.
	 */
	switch (f_ckmtab(magicbuf, mread, 1)) {
		case -1:	/* Error */
			exit(2);
			break;
		case 0:		/* Not magic */
			break;
		default:	/* Switch is magic index */
			(void) putchar('\n');
			fd_cleanup();
			return (0);
			/* NOTREACHED */
			break;
	}

	if (dflg || !M_flg) {
		/*
		 * default position-dependent tests,
		 * plus non-default magic tests, if any
		 */
		switch (def_position_tests(file)) {
			case -1:	/* error */
				fd_cleanup();
				return (1);
			case 1:	/* matching type found */
				fd_cleanup();
				return (0);
				/* NOTREACHED */
				break;
			case 0:		/* no matching type found */
				break;
		}
		/* default context-sensitive tests */
		def_context_tests();
	} else {
		/* no more tests to apply; no match was found */
		(void) printf(gettext("data\n"));
	}
	fd_cleanup();
	return (0);
}

/*
 * def_position_tests() - applies default position-sensitive tests,
 *	looking for values in specific positions in the file.
 *	These are followed by default (followed by possibly some
 *	non-default) magic file tests.
 *
 *	All position-sensitive tests, default or otherwise, must
 *	be applied before context-sensitive tests, to avoid
 *	false context-sensitive matches.
 *
 *	Returns -1 on error which should result in error (non-zero)
 *	exit status for the file utility.
 *	Returns 0 if no matching file type found.
 *	Returns 1 if matching file type found.
 */

static int
def_position_tests(char *file)
{
	if (sccs()) {	/* look for "1hddddd" where d is a digit */
		(void) printf("sccs \n");
		return (1);
	}
	if (fbuf[0] == '#' && fbuf[1] == '!' && shellscript(fbuf+2, &mbuf))
		return (1);

	if (elf_check(file) == 0) {
		(void) putchar('\n');
		return (1);
	} else if (*(int *)fbuf == CORE_MAGIC) {
#if !defined(_LP64)
		struct core *corep = (struct core *)fbuf;
#endif

		(void) printf("a.out core file");

#if !defined(_LP64)
		if (*(corep->c_cmdname) != '\0')
			(void) printf(" from '%s'", corep->c_cmdname);
#endif
		(void) putchar('\n');
		return (1);
	}

	/*
	 * Runtime linker (ld.so.1) configuration file.
	 */
	if (is_rtld_config())
		return (1);

	/*
	 * ZIP files, JAR files, and Java executables
	 */
	if (zipfile(fbuf, ifd))
		return (1);

	if (is_crash_dump(fbuf, ifd))
		return (1);

	/*
	 * ChecK against Magic Table entries.
	 * The magic entries checked here always start with default
	 * magic tests and may be followed by other, non-default magic
	 * tests.  If no default tests are to be executed, all the
	 * magic tests should have been in the first magic table.
	 */
	switch (f_ckmtab(magicbuf, mread, 0)) {
		case -1:	/* Error */
			exit(2);
			break;
		case 0:		/* Not magic */
			return (0);
			/* NOTREACHED */
			break;
		default:	/* Switch is magic index */

			/*
			 * f_ckmtab recognizes file type,
			 * check if it is PostScript.
			 * if not, check if elf or a.out
			 */
			if (magicbuf[0] == '%' && magicbuf[1] == '!') {
				(void) putchar('\n');
			} else {

				/*
				 * Check that the file is executable (dynamic
				 * objects must be executable to be exec'ed,
				 * shared objects need not be, but by convention
				 * should be executable).
				 *
				 * Note that we should already have processed
				 * the file if it was an ELF file.
				 */
				ar_coff_or_aout(elffd);
				(void) putchar('\n');
			}
			return (1);
			/* NOTREACHED */
			break;
	}

	return (0);	/* file was not identified */
}

/*
 * def_context_tests() - default context-sensitive tests.
 *	These are the last tests to be applied.
 *	If no match is found, prints out "data".
 */

static void
def_context_tests(void)
{
	int	j;
	int	nl;
	char	ch;
	int	len;

	if (ccom() == 0)
		goto notc;
	while (fbuf[i] == '#') {
		j = i;
		while (fbuf[i++] != '\n') {
			if (i - j > 255) {
				(void) printf(gettext("data\n"));
				return;
			}
			if (i >= fbsz)
				goto notc;
		}
		if (ccom() == 0)
			goto notc;
	}
check:
	if (lookup(c) == 1) {
		while ((ch = fbuf[i]) != ';' && ch != '{') {
			if ((len = mblen(&fbuf[i], MB_CUR_MAX)) <= 0)
				len = 1;
			i += len;
			if (i >= fbsz)
				goto notc;
		}
		(void) printf(gettext("c program text"));
		goto outa;
	}
	nl = 0;
	while (fbuf[i] != '(') {
		if (fbuf[i] <= 0)
			goto notas;
		if (fbuf[i] == ';') {
			i++;
			goto check;
		}
		if (fbuf[i++] == '\n')
			if (nl++ > 6)
				goto notc;
		if (i >= fbsz)
			goto notc;
	}
	while (fbuf[i] != ')') {
		if (fbuf[i++] == '\n')
			if (nl++ > 6)
				goto notc;
		if (i >= fbsz)
			goto notc;
	}
	while (fbuf[i] != '{') {
		if ((len = mblen(&fbuf[i], MB_CUR_MAX)) <= 0)
			len = 1;
		if (fbuf[i] == '\n')
			if (nl++ > 6)
				goto notc;
		i += len;
		if (i >= fbsz)
			goto notc;
	}
	(void) printf(gettext("c program text"));
	goto outa;
notc:
	i = 0;			/* reset to begining of file again */
	while (fbuf[i] == 'c' || fbuf[i] == 'C'|| fbuf[i] == '!' ||
	    fbuf[i] == '*' || fbuf[i] == '\n') {
		while (fbuf[i++] != '\n')
			if (i >= fbsz)
				goto notfort;
	}
	if (lookup(fort) == 1) {
		(void) printf(gettext("fortran program text"));
		goto outa;
	}
notfort:			/* looking for assembler program */
	i = 0;			/* reset to beginning of file again */
	if (ccom() == 0)	/* assembler programs may contain */
				/* c-style comments */
		goto notas;
	if (ascom() == 0)
		goto notas;
	j = i - 1;
	if (fbuf[i] == '.') {
		i++;
		if (lookup(as) == 1) {
			(void) printf(gettext("assembler program text"));
			goto outa;
		} else if (j != -1 && fbuf[j] == '\n' && isalpha(fbuf[j + 2])) {
			(void) printf(
			    gettext("[nt]roff, tbl, or eqn input text"));
			goto outa;
		}
	}
	while (lookup(asc) == 0) {
		if (ccom() == 0)
			goto notas;
		if (ascom() == 0)
			goto notas;
		while (fbuf[i] != '\n' && fbuf[i++] != ':') {
			if (i >= fbsz)
				goto notas;
		}
		while (fbuf[i] == '\n' || fbuf[i] == ' ' || fbuf[i] == '\t')
			if (i++ >= fbsz)
				goto notas;
		j = i - 1;
		if (fbuf[i] == '.') {
			i++;
			if (lookup(as) == 1) {
				(void) printf(
				    gettext("assembler program text"));
				goto outa;
			} else if (fbuf[j] == '\n' && isalpha(fbuf[j+2])) {
				(void) printf(
				    gettext("[nt]roff, tbl, or eqn input "
				    "text"));
				goto outa;
			}
		}
	}
	(void) printf(gettext("assembler program text"));
	goto outa;
notas:
	/* start modification for multibyte env */
	IS_ascii = 1;
	if (fbsz < FBSZ)
		Max = fbsz;
	else
		Max = FBSZ - MB_LEN_MAX; /* prevent cut of wchar read */
	/* end modification for multibyte env */

	for (i = 0; i < Max; /* null */)
		if (fbuf[i] & 0200) {
			IS_ascii = 0;
			if ((fbuf[0] == '\100') &&
			    ((uchar_t)fbuf[1] == (uchar_t)'\357')) {
				(void) printf(gettext("troff output\n"));
				return;
			}
		/* start modification for multibyte env */
			if ((length = mbtowc(&wchar, &fbuf[i], MB_CUR_MAX))
			    <= 0 || !iswprint(wchar)) {
				(void) printf(gettext("data\n"));
				return;
			}
			i += length;
		}
		else
			i++;
	i = fbsz;
		/* end modification for multibyte env */
	if (mbuf.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))
		(void) printf(gettext("commands text"));
	else if (troffint(fbuf, fbsz))
		(void) printf(gettext("troff intermediate output text"));
	else if (english(fbuf, fbsz))
		(void) printf(gettext("English text"));
	else if (IS_ascii)
		(void) printf(gettext("ascii text"));
	else
		(void) printf(gettext("text")); /* for multibyte env */
outa:
	/*
	 * This code is to make sure that no MB char is cut in half
	 * while still being used.
	 */
	fbsz = (fbsz < FBSZ ? fbsz : fbsz - MB_CUR_MAX + 1);
	while (i < fbsz) {
		if (isascii(fbuf[i])) {
			i++;
			continue;
		} else {
			if ((length = mbtowc(&wchar, &fbuf[i], MB_CUR_MAX))
			    <= 0 || !iswprint(wchar)) {
				(void) printf(gettext(" with garbage\n"));
				return;
			}
			i = i + length;
		}
	}
	(void) printf("\n");
}

static int
troffint(char *bp, int n)
{
	int k;

	i = 0;
	for (k = 0; k < 6; k++) {
		if (lookup(troff) == 0)
			return (0);
		if (lookup(troff) == 0)
			return (0);
		while (i < n && bp[i] != '\n')
			i++;
		if (i++ >= n)
			return (0);
	}
	return (1);
}

static void
ar_coff_or_aout(int elffd)
{
	Elf *elf;

	/*
	 * Get the files elf descriptor and process it as an elf or
	 * a.out (4.x) file.
	 */

	elf = elf_begin(elffd, ELF_C_READ, (Elf *)0);
	switch (elf_kind(elf)) {
		case ELF_K_AR :
			(void) printf(gettext(", not a dynamic executable "
			    "or shared object"));
			break;
		case ELF_K_COFF:
			(void) printf(gettext(", unsupported or unknown "
			    "file type"));
			break;
		default:
			/*
			 * This is either an unknown file or an aout format
			 * At this time, we don't print dynamic/stripped
			 * info. on a.out or non-Elf binaries.
			 */
			break;
	}
	(void) elf_end(elf);
}


static void
print_elf_type(Elf_Info EI)
{
	switch (EI.type) {
	case ET_NONE:
		(void) printf(" %s", gettext("unknown type"));
		break;
	case ET_REL:
		(void) printf(" %s", gettext("relocatable"));
		break;
	case ET_EXEC:
		(void) printf(" %s", gettext("executable"));
		break;
	case ET_DYN:
		(void) printf(" %s", gettext("dynamic lib"));
		break;
	default:
		break;
	}
}

static void
print_elf_machine(int machine)
{
	/*
	 * This table must be kept in sync with the EM_ constants
	 * in /usr/include/sys/elf.h.
	 */
	static const char *mach_str[EM_NUM] = {
		[EM_NONE] = "unknown machine",
		[EM_M32] = "WE32100",
		[EM_SPARC] = "SPARC",
		[EM_386] = "80386",
		[EM_68K] = "M68000",
		[EM_88K] = "M88000",
		[EM_486] = "80486",
		[EM_860] = "i860",
		[EM_MIPS] = "MIPS RS3000 Big-Endian",
		[EM_S370] = "S/370",
		[EM_MIPS_RS3_LE] = "MIPS RS3000 Little-Endian",
		[EM_RS6000] = "MIPS RS6000",
		[EM_PA_RISC] = "PA-RISC",
		[EM_nCUBE] = "nCUBE",
		[EM_VPP500] = "VPP500",
		[EM_SPARC32PLUS] = "SPARC32PLUS",
		[EM_960] = "i960",
		[EM_PPC] = "PowerPC",
		[EM_PPC64] = "PowerPC64",
		[EM_S390] = "S/390",
		[EM_V800] = "V800",
		[EM_FR20] = "FR20",
		[EM_RH32] = "RH32",
		[EM_RCE] = "RCE",
		[EM_ARM] = "ARM",
		[EM_ALPHA] = "Alpha",
		[EM_SH] = "S/390",
		[EM_SPARCV9] = "SPARCV9",
		[EM_TRICORE] = "Tricore",
		[EM_ARC] = "ARC",
		[EM_H8_300] = "H8/300",
		[EM_H8_300H] = "H8/300H",
		[EM_H8S] = "H8S",
		[EM_H8_500] = "H8/500",
		[EM_IA_64] = "IA64",
		[EM_MIPS_X] = "MIPS-X",
		[EM_COLDFIRE] = "Coldfire",
		[EM_68HC12] = "M68HC12",
		[EM_MMA] = "MMA",
		[EM_PCP] = "PCP",
		[EM_NCPU] = "nCPU",
		[EM_NDR1] = "NDR1",
		[EM_STARCORE] = "Starcore",
		[EM_ME16] = "ME16",
		[EM_ST100] = "ST100",
		[EM_TINYJ] = "TINYJ",
		[EM_AMD64] = "AMD64",
		[EM_PDSP] = "PDSP",
		[EM_FX66] = "FX66",
		[EM_ST9PLUS] = "ST9 PLUS",
		[EM_ST7] = "ST7",
		[EM_68HC16] = "68HC16",
		[EM_68HC11] = "68HC11",
		[EM_68HC08] = "68H08",
		[EM_68HC05] = "68HC05",
		[EM_SVX] = "SVX",
		[EM_ST19] = "ST19",
		[EM_VAX] = "VAX",
		[EM_CRIS] = "CRIS",
		[EM_JAVELIN] = "Javelin",
		[EM_FIREPATH] = "Firepath",
		[EM_ZSP] = "ZSP",
		[EM_MMIX] = "MMIX",
		[EM_HUANY] = "HUANY",
		[EM_PRISM] = "Prism",
		[EM_AVR] = "AVR",
		[EM_FR30] = "FR30",
		[EM_D10V] = "D10V",
		[EM_D30V] = "D30V",
		[EM_V850] = "V850",
		[EM_M32R] = "M32R",
		[EM_MN10300] = "MN10300",
		[EM_MN10200] = "MN10200",
		[EM_PJ] = "picoJava",
		[EM_OPENRISC] = "OpenRISC",
		[EM_ARC_A5] = "Tangent-A5",
		[EM_XTENSA] = "Xtensa",

		[EM_VIDEOCORE] = "Videocore",
		[EM_TMM_GPP] = "TMM_GPP",
		[EM_NS32K] = "NS32K",
		[EM_TPC] = "TPC",
		[EM_SNP1K] = "SNP1K",
		[EM_ST200] = "ST200",
		[EM_IP2K] = "IP2K",
		[EM_MAX] = "MAX",
		[EM_CR] = "CompactRISC",
		[EM_F2MC16] = "F2MC16",
		[EM_MSP430] = "MSP430",
		[EM_BLACKFIN] = "Blackfin",
		[EM_SE_C33] = "S1C33",
		[EM_SEP] = "SEP",
		[EM_ARCA] = "Arca",
		[EM_UNICORE] = "Unicore",
		[EM_EXCESS] = "eXcess",
		[EM_DXP] = "DXP",
		[EM_ALTERA_NIOS2] = "Nios 2",
		[EM_CRX] = "CompactRISC CRX",
		[EM_XGATE] = "XGATE",
		[EM_C166] = "C16x/XC16x",
		[EM_M16C] = "M16C",
		[EM_DSPIC30F] = "dsPIC30F",
		[EM_CE] = "CE RISC",
		[EM_M32C] = "M32C",
		[EM_TSK3000] = "TSK3000",
		[EM_RS08] = "RS08",
		[EM_SHARC] = "SHARC",
		[EM_ECOG2] = "eCOG2",
		[EM_SCORE7] = "SCORE7",
		[EM_DSP24] = "DSP24",
		[EM_VIDEOCORE3] = "Videocore III",
		[EM_LATTICEMICO32] = "LATTICEMICO32",
		[EM_SE_C17] = "SE_C17",
		[EM_TI_C6000] = "TMS320C6000",
		[EM_TI_C2000] = "TMS320C2000",
		[EM_TI_C5500] = "TMS320C55x",
		[EM_TI_ARP32] = "ASRP32",
		[EM_TI_PRU] = "TI_PRU",
		[EM_MMDSP_PLUS] = "MMDSP_PLUS",
		[EM_CYPRESS_M8C] = "M8C",
		[EM_R32C] = "R32C",
		[EM_TRIMEDIA] = "TriMedia",
		[EM_QDSP6] = "QDSP6",
		[EM_8051] = "8051",
		[EM_STXP7X] = "STxP7x",
		[EM_NDS32] = "NDS32",
		[EM_ECOG1] = "eCOG1X",
		[EM_MAXQ30] = "MAXQ30",
		[EM_XIMO16] = "XIMO16",
		[EM_MANIK] = "M2000",
		[EM_CRAYNV2] = "CRAYNV2",
		[EM_RX] = "RX",
		[EM_METAG] = "METAG",
		[EM_MCST_ELBRUS] = "Elbrus",
		[EM_ECOG16] = "eCOG16",
		[EM_CR16] = "CR16",
		[EM_ETPU] = "ETPU",
		[EM_SLE9X] = "SLE9X",
		[EM_L10M] = "L10M",
		[EM_K10M] = "K10M",

		[EM_AARCH64] = "aarch64",

		[EM_AVR32] = "AVR32",
		[EM_STM8] = "STM8",
		[EM_TILE64] = "TILE64",
		[EM_TILEPRO] = "TILEPRO",
		[EM_MICROBLAZE] = "MicroBlaze",
		[EM_CUDA] = "CUDA",
		[EM_TILEGX] = "TILE-Gx",
		[EM_CLOUDSHIELD] = "CloudShield",
		[EM_COREA_1ST] = "CORE-A 1st",
		[EM_COREA_2ND] = "CORE-A 2nd",
		[EM_ARC_COMPACT2] = "ARCompact V2",
		[EM_OPEN8] = "Open8",
		[EM_RL78] = "RL78",
		[EM_VIDEOCORE5] = "VideoCore V",
		[EM_78KOR] = "78KOR",
		[EM_56800EX] = "56800EX",
		[EM_BA1] = "BA1",
		[EM_BA2] = "BA2",
		[EM_XCORE] = "xCORE",
		[EM_MCHP_PIC] = "MCHP_PIC",
		[EM_KM32] = "KM32",
		[EM_KMX32] = "KMX32",
		[EM_KMX16] = "KMX16",
		[EM_KMX8] = "KMX8",
		[EM_KVARC] = "KVARC",
		[EM_CDP] = "CDP",
		[EM_COGE] = "COGE",
		[EM_COOL] = "CoolEngine",
		[EM_NORC] = "NORC",
		[EM_CSR_KALIMBA] = "Kalimba",
		[EM_Z80] = "Zilog Z80",
		[EM_VISIUM] = "VISIUMcore",
		[EM_FT32] = "FT32",
		[EM_MOXIE] = "Moxie",
		[EM_AMDGPU] = "AMD GPU",
		[EM_RISCV] = "RISC-V"
	};
	/* If new machine is added, refuse to compile until we're updated */
#if EM_NUM != 244
#error "Number of known ELF machine constants has changed"
#endif

	const char *str;

	if ((machine < EM_NONE) || (machine >= EM_NUM))
		machine = EM_NONE;

	str = mach_str[machine];
	if (str)
		(void) printf(" %s", str);
}

static void
print_elf_datatype(int datatype)
{
	switch (datatype) {
	case ELFDATA2LSB:
		(void) printf(" LSB");
		break;
	case ELFDATA2MSB:
		(void) printf(" MSB");
		break;
	default:
		break;
	}
}

static void
print_elf_class(int class)
{
	switch (class) {
	case ELFCLASS32:
		(void) printf(" %s", gettext("32-bit"));
		break;
	case ELFCLASS64:
		(void) printf(" %s", gettext("64-bit"));
		break;
	default:
		break;
	}
}

static void
print_elf_flags(Elf_Info EI)
{
	unsigned int flags;

	flags = EI.flags;
	switch (EI.machine) {
	case EM_SPARCV9:
		if (flags & EF_SPARC_EXT_MASK) {
			if (flags & EF_SPARC_SUN_US3) {
				(void) printf("%s", gettext(
				    ", UltraSPARC3 Extensions Required"));
			} else if (flags & EF_SPARC_SUN_US1) {
				(void) printf("%s", gettext(
				    ", UltraSPARC1 Extensions Required"));
			}
			if (flags & EF_SPARC_HAL_R1)
				(void) printf("%s", gettext(
				    ", HaL R1 Extensions Required"));
		}
		break;
	case EM_SPARC32PLUS:
		if (flags & EF_SPARC_32PLUS)
			(void) printf("%s", gettext(", V8+ Required"));
		if (flags & EF_SPARC_SUN_US3) {
			(void) printf("%s",
			    gettext(", UltraSPARC3 Extensions Required"));
		} else if (flags & EF_SPARC_SUN_US1) {
			(void) printf("%s",
			    gettext(", UltraSPARC1 Extensions Required"));
		}
		if (flags & EF_SPARC_HAL_R1)
			(void) printf("%s",
			    gettext(", HaL R1 Extensions Required"));
		break;
	default:
		break;
	}
}

/*
 * check_ident:	checks the ident field of the presumeably
 *		elf file. If check fails, this is not an
 *		elf file.
 */
static int
check_ident(unsigned char *ident, int fd)
{
	int class;
	if (pread64(fd, ident, EI_NIDENT, 0) != EI_NIDENT)
		return (ELF_READ_FAIL);
	class = ident[EI_CLASS];
	if (class != ELFCLASS32 && class != ELFCLASS64)
		return (ELF_READ_FAIL);
	if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
	    ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3)
		return (ELF_READ_FAIL);

	return (ELF_READ_OKAY);
}

static int
elf_check(char *file)
{
	Elf_Info EInfo;
	int class, version, format;
	unsigned char ident[EI_NIDENT];

	(void) memset(&EInfo, 0, sizeof (Elf_Info));
	EInfo.file = file;

	/*
	 * Verify information in file indentifier.
	 * Return quietly if not elf; Different type of file.
	 */
	if (check_ident(ident, elffd) == ELF_READ_FAIL)
		return (1);

	/*
	 * Read the elf headers for processing and get the
	 * get the needed information in Elf_Info struct.
	 */
	class = ident[EI_CLASS];
	if (class == ELFCLASS32) {
		if (elf_read32(elffd, &EInfo) == ELF_READ_FAIL) {
			(void) fprintf(stderr, gettext("%s: %s: can't "
			    "read ELF header\n"), File, file);
			return (1);
		}
	} else if (class == ELFCLASS64) {
		if (elf_read64(elffd, &EInfo) == ELF_READ_FAIL) {
			(void) fprintf(stderr, gettext("%s: %s: can't "
			    "read ELF header\n"), File, file);
			return (1);
		}
	} else {
		/* something wrong */
		return (1);
	}

	/* version not in ident then 1 */
	version = ident[EI_VERSION] ? ident[EI_VERSION] : 1;

	format = ident[EI_DATA];
	(void) printf("%s", gettext("ELF"));
	print_elf_class(class);
	print_elf_datatype(format);
	print_elf_type(EInfo);

	if (EInfo.core_type != EC_NOTCORE) {
		/* Print what kind of core is this */
		if (EInfo.core_type == EC_OLDCORE)
			(void) printf(" %s", gettext("pre-2.6 core file"));
		else
			(void) printf(" %s", gettext("core file"));
	}

	/* Print machine info */
	print_elf_machine(EInfo.machine);

	/* Print Version */
	if (version == 1)
		(void) printf(" %s %d", gettext("Version"), version);

	if (EInfo.kmod) {
		(void) printf(", %s", gettext("kernel module"));
	}

	/* Print Flags */
	print_elf_flags(EInfo);

	/* Last bit, if it is a core */
	if (EInfo.core_type != EC_NOTCORE) {
		/* Print the program name that dumped this core */
		(void) printf(gettext(", from '%s'"), EInfo.fname);
		return (0);
	}

	/* Print Capabilities */
	if (EInfo.cap_str[0] != '\0')
		(void) printf(" [%s]", EInfo.cap_str);

	if ((EInfo.type != ET_EXEC) && (EInfo.type != ET_DYN))
		return (0);

	/* Print if it is dynamically linked */
	if (EInfo.dynamic)
		(void) printf(gettext(", dynamically linked"));
	else
		(void) printf(gettext(", statically linked"));

	/* Printf it it is stripped */
	if (EInfo.stripped & E_SYMTAB) {
		(void) printf(gettext(", not stripped"));
		if (!(EInfo.stripped & E_DBGINF)) {
			(void) printf(gettext(
			    ", no debugging information available"));
		}
	} else {
		(void) printf(gettext(", stripped"));
	}

	return (0);
}

/*
 * is_rtld_config - If file is a runtime linker config file, prints
 * the description and returns True (1). Otherwise, silently returns
 * False (0).
 */
int
is_rtld_config(void)
{
	Rtc_id *id;

	if ((fbsz >= sizeof (*id)) && RTC_ID_TEST(fbuf)) {
		(void) printf(gettext("Runtime Linking Configuration"));
		id = (Rtc_id *) fbuf;
		print_elf_class(id->id_class);
		print_elf_datatype(id->id_data);
		print_elf_machine(id->id_machine);
		(void) printf("\n");
		return (1);
	}

	return (0);
}

/*
 * lookup -
 * Attempts to match one of the strings from a list, 'tab',
 * with what is in the file, starting at the current index position 'i'.
 * Looks past any initial whitespace and expects whitespace or other
 * delimiting characters to follow the matched string.
 * A match identifies the file as being 'assembler', 'fortran', 'c', etc.
 * Returns 1 for a successful match, 0 otherwise.
 */
static int
lookup(char **tab)
{
	register char	r;
	register int	k, j, l;

	while (fbuf[i] == ' ' || fbuf[i] == '\t' || fbuf[i] == '\n')
		i++;
	for (j = 0; tab[j] != 0; j++) {
		l = 0;
		for (k = i; ((r = tab[j][l++]) == fbuf[k] && r != '\0'); k++)
			;
		if (r == '\0')
			if (fbuf[k] == ' ' || fbuf[k] == '\n' ||
			    fbuf[k] == '\t' || fbuf[k] == '{' ||
			    fbuf[k] == '/') {
				i = k;
				return (1);
			}
	}
	return (0);
}

/*
 * ccom -
 * Increments the current index 'i' into the file buffer 'fbuf' past any
 * whitespace lines and C-style comments found, starting at the current
 * position of 'i'.  Returns 1 as long as we don't increment i past the
 * size of fbuf (fbsz).  Otherwise, returns 0.
 */

static int
ccom(void)
{
	register char	cc;
	int		len;

	while ((cc = fbuf[i]) == ' ' || cc == '\t' || cc == '\n')
		if (i++ >= fbsz)
			return (0);
	if (fbuf[i] == '/' && fbuf[i+1] == '*') {
		i += 2;
		while (fbuf[i] != '*' || fbuf[i+1] != '/') {
			if (fbuf[i] == '\\')
				i++;
			if ((len = mblen(&fbuf[i], MB_CUR_MAX)) <= 0)
				len = 1;
			i += len;
			if (i >= fbsz)
				return (0);
		}
		if ((i += 2) >= fbsz)
			return (0);
	}
	if (fbuf[i] == '\n')
		if (ccom() == 0)
			return (0);
	return (1);
}

/*
 * ascom -
 * Increments the current index 'i' into the file buffer 'fbuf' past
 * consecutive assembler program comment lines starting with ASCOMCHAR,
 * starting at the current position of 'i'.
 * Returns 1 as long as we don't increment i past the
 * size of fbuf (fbsz).  Otherwise returns 0.
 */

static int
ascom(void)
{
	while (fbuf[i] == ASCOMCHAR) {
		i++;
		while (fbuf[i++] != '\n')
			if (i >= fbsz)
				return (0);
		while (fbuf[i] == '\n')
			if (i++ >= fbsz)
				return (0);
	}
	return (1);
}

/* look for "1hddddd" where d is a digit */
static int
sccs(void)
{
	register int j;

	if (fbuf[0] == 1 && fbuf[1] == 'h') {
		for (j = 2; j <= 6; j++) {
			if (isdigit(fbuf[j]))
				continue;
			else
				return (0);
		}
	} else {
		return (0);
	}
	return (1);
}

static int
english(char *bp, int n)
{
#define	NASC 128		/* number of ascii char ?? */
	register int	j, vow, freq, rare, len;
	register int	badpun = 0, punct = 0;
	int	ct[NASC];

	if (n < 50)
		return (0); /* no point in statistics on squibs */
	for (j = 0; j < NASC; j++)
		ct[j] = 0;
	for (j = 0; j < n; j += len) {
		if ((unsigned char)bp[j] < NASC)
			ct[bp[j]|040]++;
		switch (bp[j]) {
		case '.':
		case ',':
		case ')':
		case '%':
		case ';':
		case ':':
		case '?':
			punct++;
			if (j < n-1 && bp[j+1] != ' ' && bp[j+1] != '\n')
				badpun++;
		}
		if ((len = mblen(&bp[j], MB_CUR_MAX)) <= 0)
			len = 1;
	}
	if (badpun*5 > punct)
		return (0);
	vow = ct['a'] + ct['e'] + ct['i'] + ct['o'] + ct['u'];
	freq = ct['e'] + ct['t'] + ct['a'] + ct['i'] + ct['o'] + ct['n'];
	rare = ct['v'] + ct['j'] + ct['k'] + ct['q'] + ct['x'] + ct['z'];
	if (2*ct[';'] > ct['e'])
		return (0);
	if ((ct['>'] + ct['<'] + ct['/']) > ct['e'])
		return (0);	/* shell file test */
	return (vow * 5 >= n - ct[' '] && freq >= 10 * rare);
}


static int
shellscript(char buf[], struct stat64 *sb)
{
	char *tp, *cp, *xp, *up, *gp;

	cp = strchr(buf, '\n');
	if (cp == NULL || cp - fbuf > fbsz)
		return (0);
	for (tp = buf; tp != cp && isspace((unsigned char)*tp); tp++)
		if (!isascii(*tp))
			return (0);
	for (xp = tp; tp != cp && !isspace((unsigned char)*tp); tp++)
		if (!isascii(*tp))
			return (0);
	if (tp == xp)
		return (0);
	if (sb->st_mode & S_ISUID)
		up = gettext("set-uid ");
	else
		up = "";

	if (sb->st_mode & S_ISGID)
		gp = gettext("set-gid ");
	else
		gp = "";

	if (strncmp(xp, "/bin/sh", tp - xp) == 0)
		xp = gettext("shell");
	else if (strncmp(xp, "/bin/csh", tp - xp) == 0)
		xp = gettext("c-shell");
	else if (strncmp(xp, "/usr/sbin/dtrace", tp - xp) == 0)
		xp = gettext("DTrace");
	else
		*tp = '\0';
	/*
	 * TRANSLATION_NOTE
	 * This message is printed by file command for shell scripts.
	 * The first %s is for the translation for "set-uid " (if the script
	 *   has the set-uid bit set), or is for an empty string (if the
	 *   script does not have the set-uid bit set).
	 * Similarly, the second %s is for the translation for "set-gid ",
	 *   or is for an empty string.
	 * The third %s is for the translation for either: "shell", "c-shell",
	 *   or "DTrace", or is for the pathname of the program the script
	 *   executes.
	 */
	(void) printf(gettext("%s%sexecutable %s script\n"), up, gp, xp);
	return (1);
}

static int
get_door_target(char *file, char *buf, size_t bufsize)
{
	int fd;
	door_info_t di;
	psinfo_t psinfo;

	if ((fd = open64(file, O_RDONLY)) < 0 ||
	    door_info(fd, &di) != 0) {
		if (fd >= 0)
			(void) close(fd);
		return (-1);
	}
	(void) close(fd);

	(void) sprintf(buf, "/proc/%ld/psinfo", di.di_target);
	if ((fd = open64(buf, O_RDONLY)) < 0 ||
	    read(fd, &psinfo, sizeof (psinfo)) != sizeof (psinfo)) {
		if (fd >= 0)
			(void) close(fd);
		return (-1);
	}
	(void) close(fd);

	(void) snprintf(buf, bufsize, "%s[%ld]", psinfo.pr_fname, di.di_target);
	return (0);
}

/*
 * ZIP file header information
 */
#define	SIGSIZ		4
#define	LOCSIG		"PK\003\004"
#define	LOCHDRSIZ	30

#define	CH(b, n)	(((unsigned char *)(b))[n])
#define	SH(b, n)	(CH(b, n) | (CH(b, n+1) << 8))
#define	LG(b, n)	(SH(b, n) | (SH(b, n+2) << 16))

#define	LOCNAM(b)	(SH(b, 26))	/* filename size */
#define	LOCEXT(b)	(SH(b, 28))	/* extra field size */

#define	XFHSIZ		4		/* header id, data size */
#define	XFHID(b)	(SH(b, 0))	/* extract field header id */
#define	XFDATASIZ(b)	(SH(b, 2))	/* extract field data size */
#define	XFJAVASIG	0xcafe		/* java executables */

static int
zipfile(char *fbuf, int fd)
{
	off_t xoff, xoff_end;

	if (strncmp(fbuf, LOCSIG, SIGSIZ) != 0)
		return (0);

	xoff = LOCHDRSIZ + LOCNAM(fbuf);
	xoff_end = xoff + LOCEXT(fbuf);

	while (xoff < xoff_end) {
		char xfhdr[XFHSIZ];

		if (pread(fd, xfhdr, XFHSIZ, xoff) != XFHSIZ)
			break;

		if (XFHID(xfhdr) == XFJAVASIG) {
			(void) printf("%s\n", gettext("java archive file"));
			return (1);
		}
		xoff += sizeof (xfhdr) + XFDATASIZ(xfhdr);
	}

	/*
	 * We could just print "ZIP archive" here.
	 *
	 * However, customers may be using their own entries in
	 * /etc/magic to distinguish one kind of ZIP file from another, so
	 * let's defer the printing of "ZIP archive" to there.
	 */
	return (0);
}

static int
is_crash_dump(const char *buf, int fd)
{
	/* LINTED: pointer cast may result in improper alignment */
	const dumphdr_t *dhp = (const dumphdr_t *)buf;

	/*
	 * The current DUMP_MAGIC string covers Solaris 7 and later releases.
	 * The utsname struct is only present in dumphdr_t's with dump_version
	 * greater than or equal to 9.
	 */
	if (dhp->dump_magic == DUMP_MAGIC) {
		print_dumphdr(fd, dhp, return_uint32, NATIVE_ISA);

	} else if (dhp->dump_magic == swap_uint32(DUMP_MAGIC)) {
		print_dumphdr(fd, dhp, swap_uint32, OTHER_ISA);

	} else if (dhp->dump_magic == OLD_DUMP_MAGIC ||
	    dhp->dump_magic == swap_uint32(OLD_DUMP_MAGIC)) {
		char *isa = (dhp->dump_magic == OLD_DUMP_MAGIC ?
		    NATIVE_ISA : OTHER_ISA);
		(void) printf(gettext("SunOS 32-bit %s crash dump\n"), isa);

	} else {
		return (0);
	}

	return (1);
}

static void
print_dumphdr(const int fd, const dumphdr_t *dhp, uint32_t (*swap)(uint32_t),
    const char *isa)
{
	dumphdr_t dh;

	/*
	 * A dumphdr_t is bigger than FBSZ, so we have to manually read the
	 * rest of it.
	 */
	if (swap(dhp->dump_version) > 8 && pread(fd, &dh, sizeof (dumphdr_t),
	    (off_t)0) == sizeof (dumphdr_t)) {
		const char *c = swap(dh.dump_flags) & DF_COMPRESSED ?
		    "compressed " : "";
		const char *l = swap(dh.dump_flags) & DF_LIVE ?
		    "live" : "crash";

		(void) printf(gettext(
		    "%s %s %s %u-bit %s %s%s dump from '%s'\n"),
		    dh.dump_utsname.sysname, dh.dump_utsname.release,
		    dh.dump_utsname.version, swap(dh.dump_wordsize), isa,
		    c, l, dh.dump_utsname.nodename);
	} else {
		(void) printf(gettext("SunOS %u-bit %s crash dump\n"),
		    swap(dhp->dump_wordsize), isa);
	}
}

static void
usage(void)
{
	(void) fprintf(stderr, gettext(
	    "usage: file [-bdh] [-M mfile] [-m mfile] [-f ffile] file ...\n"
	    "       file [-bdh] [-M mfile] [-m mfile] -f ffile\n"
	    "       file -i [-bh] [-f ffile] file ...\n"
	    "       file -i [-bh] -f ffile\n"
	    "       file -c [-d] [-M mfile] [-m mfile]\n"));
	exit(2);
}

static uint32_t
swap_uint32(uint32_t in)
{
	uint32_t out;

	out = (in & 0x000000ff) << 24;
	out |= (in & 0x0000ff00) << 8; /* >> 8 << 16 */
	out |= (in & 0x00ff0000) >> 8; /* >> 16 << 8 */
	out |= (in & 0xff000000) >> 24;

	return (out);
}

static uint32_t
return_uint32(uint32_t in)
{
	return (in);
}

/*
 * Check if str is in the string list str_list.
 */
int
is_in_list(char *str)
{
	int i;

	/*
	 * Only need to compare the strlen(str_list[i]) bytes.
	 * That way .stab will match on .stab* sections, and
	 * .debug will match on .debug* sections.
	 */
	for (i = 0; debug_sections[i] != NULL; i++) {
		if (strncmp(debug_sections[i], str,
		    strlen(debug_sections[i])) == 0) {
			return (1);
		}
	}
	return (0);
}

/*
 * default_magic -
 *	allocate space for and create the default magic file
 *	name string.
 */

static void
default_magic(void)
{
	const char *msg_locale = setlocale(LC_MESSAGES, NULL);
	struct stat	statbuf;

	if ((dfile = malloc(strlen(msg_locale) + 35)) == NULL) {
		int err = errno;
		(void) fprintf(stderr, gettext("%s: malloc failed: %s\n"),
		    File, strerror(err));
		exit(2);
	}
	(void) snprintf(dfile, strlen(msg_locale) + 35,
	    "/usr/lib/locale/%s/LC_MESSAGES/magic", msg_locale);
	if (stat(dfile, &statbuf) != 0) {
		(void) strcpy(dfile, "/etc/magic");
	}
}

/*
 * add_to_mlist -
 *	Add the given magic_file filename string to the list of magic
 *	files (mlist).  This list of files will later be examined, and
 *	each magic file's entries will be added in order to
 *	the mtab table.
 *
 *	The first flag is set to 1 to add to the first list, mlist1.
 *	The first flag is set to 0 to add to the second list, mlist2.
 */

static void
add_to_mlist(char *magic_file, int first)
{
	char	**mlist;	/* ordered list of magic files */
	size_t	mlist_sz;	/* number of pointers allocated  for mlist */
	char	**mlistp;	/* next entry in mlist */
	size_t mlistp_off;

	if (first) {
		mlist = mlist1;
		mlist_sz = mlist1_sz;
		mlistp = mlist1p;
	} else {
		mlist = mlist2;
		mlist_sz = mlist2_sz;
		mlistp = mlist2p;
	}

	if (mlist == NULL) {	/* initial mlist allocation */
		if ((mlist = calloc(MLIST_SZ, sizeof (char *))) == NULL) {
			int err = errno;
			(void) fprintf(stderr, gettext("%s: malloc "
			    "failed: %s\n"), File, strerror(err));
			exit(2);
		}
		mlist_sz = MLIST_SZ;
		mlistp = mlist;
	}
	if ((mlistp - mlist) >= mlist_sz) {
		mlistp_off = mlistp - mlist;
		mlist_sz *= 2;
		if ((mlist = realloc(mlist,
		    mlist_sz * sizeof (char *))) == NULL) {
			int err = errno;
			(void) fprintf(stderr, gettext("%s: malloc "
			    "failed: %s\n"), File, strerror(err));
			exit(2);
		}
		mlistp = mlist + mlistp_off;
	}
	/*
	 * now allocate memory for and copy the
	 * magic file name string
	 */
	if ((*mlistp = malloc(strlen(magic_file) + 1)) == NULL) {
		int err = errno;
		(void) fprintf(stderr, gettext("%s: malloc failed: %s\n"),
		    File, strerror(err));
		exit(2);
	}
	(void) strlcpy(*mlistp, magic_file, strlen(magic_file) + 1);
	mlistp++;

	if (first) {
		mlist1 = mlist;
		mlist1_sz = mlist_sz;
		mlist1p = mlistp;
	} else {
		mlist2 = mlist;
		mlist2_sz = mlist_sz;
		mlist2p = mlistp;
	}
}

static void
fd_cleanup(void)
{
	if (ifd != -1) {
		(void) close(ifd);
		ifd = -1;
	}
	if (elffd != -1) {
		(void) close(elffd);
		elffd = -1;
	}
}
